#ifndef GTL_ENGINE_CORE_HEADER_GUARD
#define GTL_ENGINE_CORE_HEADER_GUARD

#include <GTL/gtlAutomember.h>
#include <GTL/gtlGeometry2D.h>
#include <GTL/gtlDebug.h>
#include <GTL/gtlCPPUtils.h>
#include <GTL/gtlStructures.h>

//#define GTL_ENGINE_USE_SSE2
#ifdef GTL_ENGINE_USE_SSE2
#include <xmmintrin.h>
typedef _MM_ALIGN16 __m128 float4;
#endif

namespace GTL
{
	namespace Engine
	{
		///************************************************************************/
		///* Geometry                                                             */
		///************************************************************************/
		typedef Geometry2D::GenericPoint2D<float> Point;
		typedef Geometry2D::GenericRectangle2D<float> Rect;

		///************************************************************************/
		///* Core                                                                 */
		///************************************************************************/
		template <typename T>
		T* Produce()
		{
			return GTL::AutoMembers::Producible::Produce<T>();
		}

#define New(type, ...) \
	FactoryFunction<type>(__VA_ARGS__)

		template <typename T>	T* FactoryFunction() { return new T; }
		template <typename T, typename A1>	T* FactoryFunction(A1 arg1) { return new T(arg1); }
		template <typename T, typename A1, typename A2>	T* FactoryFunction(A1 arg1, A2 arg2) { return new T(arg1, arg2); }

/*
		inline int& noopfunc(...) { static int a; return a; }
		inline void noop2(...) {}
*/
		///************************************************************************/
		///* Graphics                                                             */
		///************************************************************************/
		struct ColorRGBA 
		{
			union {
				struct { float R, G, B, A; };
				float RGBA[4];
			};
			ColorRGBA() {}
#ifdef GTL_ENGINE_USE_SSE2
			ColorRGBA(float4 v4) : V4(v4) {}
#endif
			ColorRGBA(float r, float g, float b) : R(r), G(g), B(b), A(1.0f) {}
			ColorRGBA(float r, float g, float b, float a) : R(r), G(g), B(b), A(a) {}
			
			ColorRGBA(GTL::Structures::DataNode* node);

			ColorRGBA operator*(const ColorRGBA& r2) const
			{
#ifdef GTL_ENGINE_USE_SSE2
				float4 v1 = _mm_load_ps(RGBA);
				float4 v2 = _mm_load_ps(r2.RGBA);
				ColorRGBA result;
				_mm_store_ss(result.RGBA, _mm_mul_ps(v1, v2));
				return result;
#else
				ColorRGBA ret = *this;
				ret.R *= r2.R;
				ret.G *= r2.G;
				ret.B *= r2.B;
				ret.A *= r2.A;
				return ret;
#endif
			}

			static ColorRGBA Red(float a = 1.0f) { return ColorRGBA(1.0f, 0.0f, 0.0f, a); }
			static ColorRGBA Green(float a = 1.0f) { return ColorRGBA(0.0f, 1.0f, 0.0f, a); }
			static ColorRGBA Blue(float a = 1.0f) { return ColorRGBA(0.0f, 0.0f, 1.0f, a); }
			static ColorRGBA White(float a = 1.0f) { return ColorRGBA(1.0f, 1.0f, 1.0f, a); }
			static ColorRGBA Black(float a = 1.0f) { return ColorRGBA(0.0f, 0.0f, 0.0f, a); }
			static ColorRGBA Cyan(float a = 1.0f) { return ColorRGBA(1.0f, 1.0f, 0.0f, a); }
			static ColorRGBA Magenta(float a = 1.0f) { return ColorRGBA(1.0f, 0.0f, 1.0f, a); }
			static ColorRGBA Yellow(float a = 1.0f) { return ColorRGBA(0.0f, 1.0f, 1.0f, a); }
			static ColorRGBA Transparent() { return ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); }
		};

		struct BlendMode { enum
		{
			Replace,
			Alpha,
			Additive,
			Multiply,
			Decal /// binary alpha
		}; };

		struct ImageRef
		{
			float Width, Height;
			virtual void Draw(const Point& pos, const ColorRGBA& color = ColorRGBA(1,1,1,1)) const = 0;
			virtual void Draw(const Rect& dest, const ColorRGBA& color = ColorRGBA(1,1,1,1)) const = 0;
			virtual void Draw(const Rect& source, const Rect& dest, const ColorRGBA& color = ColorRGBA(1,1,1,1)) const = 0;

			virtual void Draw(const Rect& source, const Point& center, const Point& dest, float rotation, const Point& scale = Point(1.0f, 1.0f), const ColorRGBA& color = ColorRGBA(1,1,1,1), int flip_flags = 0) const = 0;

      struct LockStruct
      {
        void* Data;
        int Format;
        int Pitch;
        int PixelSize;
      };
      virtual LockStruct Lock() = 0;
      virtual void Unlock() = 0;
			virtual ~ImageRef();
		};
		typedef Utilities::ReferenceCountedSmartPointer<ImageRef> Image;

		//Image NodeToImage(GTL::Structures::DataNode* node);

		struct FontRef
		{
			virtual void DrawText(const Point& pos, const char* str, const ColorRGBA& color = ColorRGBA(1,1,1,1), int length = -1) const = 0;
			virtual float GetTextWidth(const char* str, int length = -1) const = 0;
			virtual float GetHeight() const = 0;
			virtual ~FontRef();
		};
		typedef Utilities::ReferenceCountedSmartPointer<FontRef> Font;

		struct ShaderRef
		{
			virtual void Use() = 0;
			virtual void Unuse() = 0;
			virtual int32_t GetAttributeID(const char* str) = 0;
			virtual int32_t GetUniformID(const char* name) = 0;
			virtual void SetUniform(int32_t uniformID, float val) = 0;
			virtual void SetUniform(int32_t uniformID, const Point& val) = 0;
			virtual ~ShaderRef();
		};
		typedef Utilities::ReferenceCountedSmartPointer<ShaderRef> Shader;

		///************************************************************************/
		///* Service                                                              */
		///************************************************************************/
		class Game;

		class Service
		{
		public:
			virtual void Init(Game* game)
			{
				mGame = game;
				Start();
			}

			virtual void Start() = 0;
			virtual int GetPriority() const = 0;
			virtual const char* GetName() const = 0;
			virtual int Do(float dt) = 0;
			virtual void Shutdown() = 0;

			enum
			{
				Loader = 0,
				PreUpdate = 1000,
				Update = 2000,
				PostUpdate = 3000,
				Draw = 4000,
				PostDraw = 5000,
			};
		protected:
			Game* mGame;
		};
		struct ServiceSorter {
			bool operator()(const Service* s1, const Service* s2) {
				return s1->GetPriority() < s2->GetPriority();
			}
		};

		///************************************************************************/
		///* Logging                                                              */
		///************************************************************************/
		namespace Logging
		{
			enum
			{
				Info = 1<<0,
				Warning = 1<<1,
				Error = 1<<2,
				Resource = 1<<3,
				Trace = 1<<4,
			};

			extern int LogTypes;

			inline bool CanLog(int type)
			{
				return !!(LogTypes & type);
			}
		};

		///************************************************************************/
		///* Events                                                               */
		///************************************************************************/


		namespace GUI { class Control; }
		struct ClientConnection;

		struct GameEvent
		{
			struct CharPair { int Key; int Char; };
			static CharPair MakeCharPair(int k, int c) { CharPair p = {k, c}; return p; }

			int EventID;
			void* Sender;

			union
			{
				long long int AsInteger;
				const char* AsString;
				double AsDouble;
				void* AsVoidPtr;
				CharPair AsChar;
			};

			Point AsPoint; /// stupid union

			void SetData(int val)								{ AsInteger = val; }
			void SetData(unsigned int val)			{ AsInteger = val; }
			void SetData(const char* val)				{ AsString = val; }
			void SetData(double val)						{ AsDouble = val; }
			void SetData(const CharPair& val)		{ AsChar = val; }
			void SetData(const Point& pt)				{	AsPoint = pt; }
			template <typename T> 
			void SetData(T* val)								{ AsVoidPtr = val; }

			GUI::Control* SenderControl() const { return (GUI::Control*)Sender; }
			ClientConnection* SenderConnection() const { return (ClientConnection*)Sender; }

			static GameEvent Make(int id, void* sender)
			{
				GameEvent e;
				e.EventID = id;
				e.Sender = sender;
				return e;
			}
			template <typename T>
			static GameEvent Make(int id, void* sender, T data)
			{
				GameEvent e;
				e.EventID = id;
				e.Sender = sender;
				e.SetData(data);
				return e;
			}
			operator bool() const { return !!EventID; }

		};

		struct EventBuffer
		{
			static const int MAX_EVENTS = 64;
			size_t Count;
			GameEvent Events[MAX_EVENTS];

			bool PushEvent(const GameEvent& ev)
			{
				if (Count >= MAX_EVENTS) return false;

				Events[Count] = ev;
				Count++;
				return true;
			}

			template <typename T>
			bool PushEvent(int event, void* sender, T eventData)
			{
				if (Count >= MAX_EVENTS) return false;

				Events[Count].EventID = event;
				Events[Count].Sender = sender;
				Events[Count].SetData(eventData);
				Count++;
				return true;
			}
			GameEvent PopEvent()
			{
				GameEvent e;
				e.EventID = 0;
				if (Count)
				{
					Count--;
					return Events[Count];
				} else return e;
			}
			void ClearEvents()
			{
				Count = 0;
			}

			EventBuffer()
			{
				ClearEvents();
			}
		};

		///************************************************************************/
		///* Backend                                                              */
		///************************************************************************/

		/// Include key definitions
		#include "gtlCore_Keys.h"

		namespace Core
		{
			namespace Event
			{
				enum
				{
					CoreEvent = 'SYSE',

					CharEntered,
					QuitRequested,
					Minimized,
					Restored,
					Resized,
				};
			}
		}

		class BackendInterface
		{
		public:
      virtual void Init(Game* game, int width, int height, bool fullscreen) = 0;
			virtual void RegisterServices() = 0;
			virtual void Destroy() = 0;

			virtual ImageRef* LoadImage(const char* filename, bool is_cache_ok) = 0;
			virtual FontRef* LoadFont(const char* filename) = 0;
			virtual ShaderRef* LoadShader(const char* vshader, const char* pshader) = 0;

			virtual ImageRef* CreateRenderTarget(size_t width, size_t height) = 0;
			virtual void SetRenderTarget(ImageRef* target) = 0;

			virtual void ClearScreen(const ColorRGBA& color) = 0;
      virtual void PutPixel(const Point& pos, const ColorRGBA& color) = 0;
			virtual void DrawRect(const Rect& rect, const ColorRGBA& color, const ColorRGBA& fillcolor = ColorRGBA(0,0,0,0)) = 0;
			virtual void DrawLine(float x, float y, float x2, float y2, const ColorRGBA& color) = 0;
      virtual void LockDrawing(bool lock) = 0;

			virtual Rect GetClippingRect() = 0;
			virtual void SetClippingRect(const Rect& rect) = 0;

			virtual void SetBlendingMode(int mode) = 0;

			virtual void UnuseShader() = 0;

			virtual void DestroyImage(ImageRef* ref) = 0;
			virtual void DestroyFont(FontRef* ref) = 0;
			virtual void DestroyShader(ShaderRef* ref) = 0;

			virtual void DebugLog(const char* str, int type = Logging::Info) = 0;

			virtual uint64_t GetTimeMsec() = 0;
			virtual double GetTimeSec() = 0;

			///************************************************************************/
			/// Input
			///************************************************************************/

			struct
			{
				int Buttons;
				float X, Y, Z, W;

				GTL::Engine::Point GetPos() const { return GTL::Engine::Point(X,Y); }

			} MouseState[2];
			struct
			{
				int Keys[256];
			} KeyboardState[2];

			///************************************************************************/
			/// Logging
			///************************************************************************/

#ifdef GTL_DEBUG
			//#define GTL_ENGINE_CORE_DEFINE_LOG(X) inline void Log##X(const char*) const { }
			
#define GTL_ENGINE_CORE_DEFINE_LOG(X) void* Log##X(const char* str, ...) { \
	if (Logging::LogTypes & Logging::X) \
	{ \
		char buffer[1024]; \
		va_list args; \
		va_start (args, str); \
		vsprintf_s (buffer, 1024, str, args); \
		DebugLog(buffer, Logging:: X ); \
		va_end (args); \
		 \
	} return 0; }

#else
#define GTL_ENGINE_CORE_DEFINE_LOG(X) static inline void Log##X(const char*, ...) { }
#endif

			GTL_ENGINE_CORE_DEFINE_LOG(Info)
			GTL_ENGINE_CORE_DEFINE_LOG(Warning)
			GTL_ENGINE_CORE_DEFINE_LOG(Resource)
			GTL_ENGINE_CORE_DEFINE_LOG(Trace)
			GTL_ENGINE_CORE_DEFINE_LOG(Error)

			void NoOp() {}
		};

		extern BackendInterface* Backend;

		///************************************************************************/
		///* Implementations                                                      */
		///************************************************************************/
	}
	/// TODO: Finish me
	template <>
	inline bool NodeToObject(Engine::Image& obj, const GTL::Structures::DataNode* node)
	{
		obj = Engine::Backend->LoadImage(node->String.c_str(), true);
		return true;
	}

	/// TODO: Finish me
	template <>
	inline bool NodeToObject(Engine::Font& obj, const GTL::Structures::DataNode* node)
	{
		obj = Engine::Backend->LoadFont(node->String.c_str());
		return true;
	}

	/// TODO: Finish me
	template <>
	inline bool NodeToObject(Engine::Rect& obj, const GTL::Structures::DataNode* node)
	{
		obj = Engine::Rect();
		return true;
	}

	/// TODO: Finish me
	template <>
	bool NodeToObject(Engine::ColorRGBA& obj, const GTL::Structures::DataNode* node);
}

#endif // gtlCore_h__